package com.hjy.backend.tableManager;

import com.google.common.primitives.Bytes;
import com.hjy.backend.indexManager.BPlusTree;
import com.hjy.backend.parser.statement.SingleExpression;
import com.hjy.backend.tableManager.result.FieldCalRes;
import com.hjy.backend.transactionManager.TransactionManagerImpl;
import com.hjy.common.error.Error;
import com.hjy.backend.utils.Panic;
import com.hjy.backend.utils.ParseStringRes;
import com.hjy.backend.utils.Parser;


import java.util.Arrays;
import java.util.List;

/**
 * field 表示字段信息
 * 二进制格式为：
 * [FieldName][TypeName][IndexUid]
 * <p>
 * FileName 与 TypeName 都是以String类型进行的存储, String类型存储结构如下: [StringLength][StringData], 前4位是string的长度, 后面是str字符串
 * <p>
 * IndexUid 占 8 位, 指向了索引二叉树的根
 * <p>
 * 如果field无索引，IndexUid为0
 */
public class Field {
    long uid; // 存储这个 field 使用的dataItem
    private Table tb; // 所属于的表
    String fieldName; // 字段名
    String fieldType; // 字段类型, 有 int32, int64, string
    private long index; // 索引uid, 如果没有索引就是0
    private BPlusTree bt; // 如果这个字段是一个索引, 那么就有一个 B+ tree

    /**
     * 通过uid从VersionManager中加载一个Entry
     *
     * @param tb
     * @param uid
     * @return
     */
    public static Field loadField(Table tb, long uid) {
        byte[] raw = null;
        try {
            // 加载了一个Entry
            raw = ((TableManagerImpl) tb.tbm).vm.read(TransactionManagerImpl.SUPER_XID, uid);
        } catch (Exception e) {
            Panic.panic(e);
        }
        assert raw != null;
        return new Field(uid, tb).parseSelf(raw);
    }

    public Field(long uid, Table tb) {
        this.uid = uid;
        this.tb = tb;
    }

    public Field(Table tb, String fieldName, String fieldType, long index) {
        this.tb = tb;
        this.fieldName = fieldName;
        this.fieldType = fieldType;
        this.index = index;
    }

    /**
     * 将一个Entry记录解析成一个Field
     *
     * @param raw
     * @return
     */
    private Field parseSelf(byte[] raw) {
        int position = 0;
        // 解析字段名
        ParseStringRes res = Parser.parseString(raw);
        fieldName = res.str;
        position += res.next;
        // 解析字段类型
        res = Parser.parseString(Arrays.copyOfRange(raw, position, raw.length));
        fieldType = res.str;
        position += res.next;
        // 查看是否存在索引
        this.index = Parser.parseLong(Arrays.copyOfRange(raw, position, position + 8));
        if (index != 0) {
            try {
                // 有索引的话, 就需要加载一颗B+tree了
                bt = BPlusTree.load(index, ((TableManagerImpl) tb.tbm).dm);
            } catch (Exception e) {
                Panic.panic(e);
            }
        }
        return this;
    }

    /**
     * 创建一个Field
     *
     * @param tb
     * @param xid
     * @param fieldName
     * @param fieldType
     * @param indexed
     * @return
     * @throws Exception
     */
    public static Field createField(Table tb, long xid, String fieldName, String fieldType, boolean indexed) throws Exception {
        // 首先检查类型
        typeCheck(fieldType);
        // 创建Filed
        Field f = new Field(tb, fieldName, fieldType, 0);
        // 如果有索引, 我们就加载一颗B+tree
        if (indexed) {
            // 新树根节点的Uid
            long index = BPlusTree.create(((TableManagerImpl) tb.tbm).dm);
            BPlusTree bt = BPlusTree.load(index, ((TableManagerImpl) tb.tbm).dm);
            f.index = index;
            f.bt = bt;
        }
        // 持久化
        f.persistSelf(xid);
        return f;
    }

    /**
     * 根据二进制格式, 通过VersionManager存储到文件
     *
     * @param xid
     * @throws Exception
     */
    private void persistSelf(long xid) throws Exception {
        byte[] nameRaw = Parser.string2Byte(fieldName);
        byte[] typeRaw = Parser.string2Byte(fieldType);
        byte[] indexRaw = Parser.long2Byte(index);
        this.uid = ((TableManagerImpl) tb.tbm).vm.insert(xid, Bytes.concat(nameRaw, typeRaw, indexRaw));
    }

    /**
     * 由于只支持类型 int32 int64 string , 不是这三种类型抛异常
     *
     * @param fieldType
     * @throws Exception
     */
    private static void typeCheck(String fieldType) throws Exception {
        if (!"int32".equals(fieldType) && !"int64".equals(fieldType) && !"string".equals(fieldType)) {
            throw Error.InvalidFieldException;
        }
    }

    /**
     * 是否是索引字段
     *
     * @return
     */
    public boolean isIndexed() {
        return index != 0;
    }

    /**
     * 索引树插入一个数据项, key是键
     *
     * @param key
     * @param uid
     * @throws Exception
     */
    public void insert(Object key, long uid) throws Exception {
        long uKey = value2UKey(key);
        bt.insert(uKey, uid);
    }

    /**
     * 查询在key left~right 之间的所有uid
     *
     * @param left
     * @param right
     * @return
     * @throws Exception
     */
    public List<Long> search(long left, long right) throws Exception {
        return bt.searchRange(left, right);
    }

    /**
     * string 类型转换为对应的字段类型值
     *
     * @param str
     * @return
     */
    public Object string2Value(String str) {
        switch (fieldType) {
            case "int32":
                return Integer.parseInt(str);
            case "int64":
                return Long.parseLong(str);
            case "string":
                return str;
        }
        return null;
    }

    /**
     * 将对象转换成uKey, 用于索引排序
     *
     * @param key
     * @return
     */
    public long value2UKey(Object key) {
        long uid = 0;
        switch (fieldType) {
            case "string":
                uid = Parser.str2Uid((String) key);
                break;
            case "int32":
                int uint = (int) key;
                return (long) uint;
            case "int64":
                uid = (long) key;
                break;
        }
        return uid;
    }

    /**
     * 将对应的值根据字段类型转换为字节数组
     *
     * @param v
     * @return
     */
    public byte[] value2Raw(Object v) {
        byte[] raw = null;
        switch (fieldType) {
            case "int32":
                raw = Parser.int2Byte((int) v);
                break;
            case "int64":
                raw = Parser.long2Byte((long) v);
                break;
            case "string":
                raw = Parser.string2Byte((String) v);
                break;
        }
        return raw;
    }

    class ParseValueRes {
        Object v; // 解析的结果
        int shift; // 偏移
    }

    /**
     * 解析字段值
     *
     * @param raw
     * @return
     */
    public ParseValueRes parserValue(byte[] raw) {
        ParseValueRes res = new ParseValueRes();
        switch (fieldType) {
            case "int32":
                res.v = Parser.parseInt(Arrays.copyOf(raw, 4));
                res.shift = 4;
                break;
            case "int64":
                res.v = Parser.parseLong(Arrays.copyOf(raw, 8));
                res.shift = 8;
                break;
            case "string":
                ParseStringRes r = Parser.parseString(raw);
                res.v = r.str;
                res.shift = r.next;
                break;
        }
        return res;
    }

    /**
     * 将字段值转换为String类型
     *
     * @param v
     * @return
     */
    public String printValue(Object v) {
        String str = null;
        switch (fieldType) {
            case "int32":
                str = String.valueOf((int) v);
                break;
            case "int64":
                str = String.valueOf((long) v);
                break;
            case "string":
                str = (String) v;
                break;
        }
        return str;
    }

    @Override
    public String toString() {
        return new StringBuilder("(")
                .append(fieldName)
                .append(", ")
                .append(fieldType)
                .append(index != 0 ? ", Index" : ", NoIndex")
                .append(")")
                .toString();
    }

    /**
     * 计算逻辑表达式, 如 id>0 , id = 10
     *
     * @param exp
     * @return 检索到的 uid 的key的范围
     * @throws Exception
     */
    public FieldCalRes calExp(SingleExpression exp) throws Exception {
        Object v = null;
        FieldCalRes res = new FieldCalRes();
        switch (exp.compareOp) {
            case "<":
                // 左区间就肯定是0开始辣
                res.left = 0;
                v = string2Value(exp.value);
                // 转换为key
                res.right = value2UKey(v);
                if (res.right > 0) {
                    res.right--;
                }
                break;
            case "=":
                v = string2Value(exp.value);
                res.left = value2UKey(v);
                res.right = res.left;
                break;
            case ">":
                res.right = Long.MAX_VALUE;
                v = string2Value(exp.value);
                res.left = value2UKey(v) + 1;
                break;
        }
        return res;
    }
}
